import { Group } from "three/src/objects/Group";
import { BaseTileMap } from "./BaseTileMap";
import { App, Point } from "../Engine";
import Events from "../application/constants/Events";
import GeoUtils from "../utils/GeoUtils";
import Helper from "../utils/Helper";
import { MapCarpet } from "../component/models/MapCarpet";
import { Mesh, Plane, PlaneGeometry, Vector3 } from "three";
import Enum from "../application/constants/Enum";
import PoiCollisionManager from "./utils/PoiCollisionManager";
import MeshUtil from "../renderExtension/utils/MeshUtil";
import Config from "../application/constants/Config";
import Const from "../application/constants/Const";
import Orbit from "../processor/Orbit";
/**
 * 地图底图的容器类
 */
class Map extends Group {
    public app: App;
    public zoom;
    private debounceLoadEvent; //地图加载事件，带防抖功能
    // private debounceZoomEvent; //zoom事件，带防抖功能
    // private layerMap: Map<string, Object3D>;
    private layerMap = {};
    // private tileRange: { minCol: number, maxCol: number, minRow: number, maxRow: number, zoom: number };
    private frustumBound;
    private isContainDem: boolean = false; //是否包含dem图层
    public mapVirtualCarpet: MapCarpet;
    // private modelCollisionCameraArray: Array<Object3D>; //和相机碰撞的model数组
    private baseTileMapNum: number = 0;
    public availableBaseMapNum: number = 1; //每次渲染时，可用baseTileMap对象的数量

    constructor() {
        super();
        this.app = App.getInstance();
        this.zoom = this.app.options.Map.zoom;
        // this.modelCollisionCameraArray = new Array<Object3D>();

        document.documentElement.style.overflow = 'hidden'; //去掉滚动条
        this.debounceLoadEvent = Helper.debounce2(this.load, 100, false); // this.load.bind(this); //
        // this.debounceZoomEvent = Helper.debounce2(this.load, 100, false); //this.zoomChangeEvent.bind(this);//

        this.initBaseTileMap();
        this.register();
    }

    public setMaxSlopeAngle(angle: number) {
        // if(angle > 80){ //如果倾斜角度大于80度，相机近处的物体可能会被裁剪掉，所以这里限制最大倾斜角度为80度
        //     angle = 80;
        // }

        this.app.options.Map.maxSlopeAngle = angle;
        let orbit: Orbit = this.app.camera.orbit;
        orbit.setMaxPolarAngle(angle);
    }

    private initBaseTileMap() {
        this.setMaxSlopeAngle(this.app.options.Map.maxSlopeAngle);

        if (!this.app.options.Map.baseLayer) {
            return;
        }

        for (let i = 0; i < this.app.options.Map.baseLayer.length; i++) {
            let layerData = this.app.options.Map.baseLayer[i];
            if(!layerData){
                continue;
            }
            
            this.addTileMap(layerData);
        }

        this.mapVirtualCarpet = new MapCarpet();
        this.app.add(this.mapVirtualCarpet);
    }

    public addTileMap(layerData) {
        if(!layerData.name){
            layerData.name = ''+this.baseTileMapNum;
        }
        layerData.mapOrder = Const.RenderOrder.Map + this.baseTileMapNum * 3;
        layerData.zIndex = this.app.options.Map.zIndex * this.baseTileMapNum;

        let tileMap = this.getTileMap(layerData.tileType, layerData.name);
        if (tileMap) {
            return;
        }

        let demUrl = layerData.demUrl;
        if (demUrl != undefined || demUrl != null) {
            this.isContainDem = true;
        }
        let tileType = layerData.tileType;
        let tileMapConfig = {
            configUrl: layerData.configUrl,
            bundleUrl: layerData.bundleUrl,
            url: layerData.url,
            urlDecimal: (layerData.urlDecimal === undefined) ? true : layerData.urlDecimal,
            srcUrl: layerData.srcUrl,
            srcUse: (layerData.srcUse === undefined) ? false : layerData.srcUse,
            wmsUrl: layerData.wmsUrl,
            wmsUrlDecimal: (layerData.wmsUrlDecimal === undefined) ? true : layerData.wmsUrlDecimal,
            isAdditionalLayer: (layerData.isAdditionalLayer === undefined) ? false : layerData.isAdditionalLayer,
            colorSpace: layerData.colorSpace,

            demUrl: demUrl,
            demUrlDecimal: (layerData.demUrlDecimal === undefined) ? true : layerData.demUrlDecimal,
            demHeightAdjust: layerData.demHeightAdjust,
            tileType: tileType,
            canNotSelect: layerData.canNotSelect,
            maxDataLevel: layerData.maxDataLevel,
            merge: layerData.merge,
            shapeMinLevel: this.app.options.ShapeConfig.shapeMinLevel,
        };
        //把tileMapConfig中没有的属性，从layerData中写入到tileMapConfig中
        tileMapConfig = { ...layerData, ...tileMapConfig };
        let baseTileMap = new BaseTileMap(tileMapConfig);

        let mapHeight = (layerData.mapHeight === undefined) ? 0 : layerData.mapHeight;

        baseTileMap.position.z += mapHeight;
        this.add(baseTileMap);
        this.baseTileMapNum++;

        if (!this.layerMap[tileType]) {
            this.layerMap[tileType] = [];
        }
        this.layerMap[tileType].push(baseTileMap);
        // this.addModelCollisionCamera(baseTileMap);

        this.loadBaseTileMap(baseTileMap);
        return baseTileMap;
    }

    public loadBaseTileMap(baseTileMap: BaseTileMap){
        let visualBound = this.computeVisualBound();
        baseTileMap.load(visualBound); //加载当前可视范围内的瓦片数据
        this.frustumBound = [];
    }

    /**
     * 获取某种地图图层
     * @param type 图层名称
     * @returns 
     */
    public getLayersByType(type) {
        let layer = this.layerMap[type];
        return layer;
    }

    public getAllLayer() {
        return this.layerMap;
    }

    /**
     * 获取某种类型图层中的某一个图层
     * @param type 图层类型
     * @param name 图层名称
     * @returns 
     */
    public getTileMap(type, name) {
        let layer = this.getLayersByType(type);
        if (!layer) {
            return null;
        }

        for (let i = 0; i < layer.length; i++) {
            if (layer[i].name === name) {
                return layer[i];
            }
        }
        return null;
    }

    /**
     * 设置地图能缩放到的最小层级
     * @param minZoom 
     */
    public setMinZoom(minZoom) {
        this.app.options.Map.minZoom = minZoom;
    }

    /**
     * 设置地图能缩放到的最大层级
     * @param minZoom 
     */
    public setMaxZoom(maxZoom) {
        this.app.options.Map.maxZoom = maxZoom;
    }

    /**
     * 通过图层的类型，删除地图图层。一个类型中可能会有很多个图层
     * @param type 图层名称
     */
    public removeTileMap(type, name) {
        let layer = this.getLayersByType(type);
        if (!layer) {
            return;
        }

        let removeIndex = -1;
        for (let i = 0; i < layer.length; i++) {
            let tileMap = layer[i];
            if (tileMap.name === name) {
                MeshUtil.dispose(tileMap);
                removeIndex = i;
                break;
            }
        }

        if (removeIndex !== -1) {
            layer.splice(removeIndex, 1);
        }
    }

    /**
     * 删除所有图层
     */
    public removeAllTileMap() {
        for (let type in this.layerMap) {
            let layer = this.getLayersByType(type);
            for (let i = 0; i < layer.length; i++) {

            }
        }
    }

    /**
     * 加载地图瓦片
     */
    public load() {
        if(!this.frustumBound || this.frustumBound.length === 0) {
            this.frustumBound = this.computeVisualBound();
        }

        for (let i = 0; i < this.frustumBound.length; i++) {
            if (!this.frustumBound[i]) {
                return;
            }
        }

        let aliveCount = 0;
        for (let i = 0; i < this.children.length; i++) {
            if (this.children[i]['load']) {
                let isLoadSuccess = this.children[i]['load'](this.frustumBound);
                if(isLoadSuccess){
                    aliveCount++;
                }
            }
        }
        this.availableBaseMapNum = aliveCount;
        this.frustumBound = [];
    }

    /** 计算底图在视椎体边界的四个顶点 */
    public computeVisualBound(): Array<Vector3> {
        let pitchAngle = this.getPitchAngle();
        let leftTopPoint;
        let rightTopPoint;
        let mapPlane: Plane = new Plane(new Vector3(0, 0, 1), 0);
        this.app.three.camera.updateMatrix();
        this.app.three.camera.updateMatrixWorld(true);
        let maxAngle = 40;
        let offsetWidth = 0;
        let offsetHeight = 0;
        if (this.isContainDem) {
            offsetWidth = window.innerWidth / 5;
            offsetHeight = window.innerHeight / 4;
        }

        if (pitchAngle >= maxAngle) { //当地图的倾斜角大于一个角度的时候，使用相机的位置和lookAtTarget构造一个向量,用这个向量和底图做相交检测；找到相交点后，用相交点和相机在平面上的投影点构造一个向量，把这个向量的长度扩大，然后和相机在平面上投影点相加，得到一个新的点，把这个点转换到屏幕坐标
            let rangeScale = 2;
            if (this.app.background.type == Enum.BackGround.Fog) {
                rangeScale = 2;
            } else if (this.app.background.type == Enum.BackGround.Sky) {
                rangeScale = 5;
            } else {
                rangeScale = 4;
            }
            let cameraTarget = GeoUtils.getCoverPoint(rangeScale);
            let cameraTargetNdc = cameraTarget.project(this.app.three.camera); //把三维点转换到归一化坐标
            let screenPoint = GeoUtils.convertNdcToScreen(cameraTargetNdc); //归一化坐标转换到屏幕坐标

            leftTopPoint = GeoUtils.convertScreenToThree(0, screenPoint.y, cameraTargetNdc.z);
            rightTopPoint = GeoUtils.convertScreenToThree(this.app.getSize().getWidth(), screenPoint.y, cameraTargetNdc.z);
        } else {//当地图的倾斜角小于一个角度的时候，把屏幕左上角和右上角的点转换到视椎体的近平面，然后从相机位置发射射线，找到在底图上的相交点
            leftTopPoint = GeoUtils.convertScreenToThree(0, 0);
            let leftTopVector = leftTopPoint.clone().sub(this.app.three.camera.position.clone()).normalize();
            rightTopPoint = GeoUtils.convertScreenToThree(this.app.getSize().getWidth(), 0);
            let rightTopVector = rightTopPoint.clone().sub(this.app.three.camera.position.clone()).normalize();
            leftTopPoint = GeoUtils.getIntersetPoint(mapPlane, leftTopVector);
            rightTopPoint = GeoUtils.getIntersetPoint(mapPlane, rightTopVector);
        }

        //把屏幕左下角和右下角的点转换到视椎体的近平面，然后从相机位置发射射线，找到在底图上的相交点
        let leftBottomPoint = GeoUtils.convertScreenToThree(-offsetWidth, this.app.getSize().getHeight() + offsetHeight);
        let leftBottomVector = leftBottomPoint.clone().sub(this.app.three.camera.position.clone()).normalize();
        let rightBottomPoint = GeoUtils.convertScreenToThree(this.app.getSize().getWidth() + offsetWidth, this.app.getSize().getHeight() + offsetHeight);
        let rightBottomVector = rightBottomPoint.clone().sub(this.app.three.camera.position.clone()).normalize();
        leftBottomPoint = GeoUtils.getIntersetPoint(mapPlane, leftBottomVector);
        rightBottomPoint = GeoUtils.getIntersetPoint(mapPlane, rightBottomVector);

        this.frustumBound = [];
        this.frustumBound.push(leftTopPoint);
        this.frustumBound.push(rightTopPoint);
        this.frustumBound.push(leftBottomPoint);
        this.frustumBound.push(rightBottomPoint);

        return this.frustumBound;
    }

    /**
     * 获取地图的边界。在一个数组中存储四个顶点，分别是上下左右，四个顶点
     * @returns 返回地图的边界
     */
    public getMapBound() {
        let zoom = this.app.map.zoom;
        let tileWidth = GeoUtils.getTileWidth(this.app.map.zoom) * 0.5;
        let tileRange: { minCol: number, maxCol: number, minRow: number, maxRow: number, zoom: number } = GeoUtils.computeTileRange(this.frustumBound, this.app.map.zoom);//根据相交点，计算要加载瓦片的范围
        tileRange = {
            minCol: Math.min(tileRange.minCol, tileRange.maxCol),
            maxCol: Math.max(tileRange.minCol, tileRange.maxCol),
            minRow: Math.min(tileRange.minRow, tileRange.maxRow),
            maxRow: Math.max(tileRange.minRow, tileRange.maxRow),
            zoom: zoom
        };
        let minX = GeoUtils.getCDevX(tileRange.minCol, zoom, 0.5);// - tileWidth;
        let minY = GeoUtils.getRDevY(tileRange.minRow, zoom, 0.5);// - tileWidth;

        let maxX = GeoUtils.getCDevX(tileRange.maxCol, zoom, 0.5);// + tileWidth;
        let maxY = GeoUtils.getRDevY(tileRange.maxRow, zoom, 0.5);// + tileWidth;

        let range = {
            minPointX: Math.min(minX, maxX) - tileWidth,
            maxPointX: Math.max(minX, maxX) + tileWidth,
            minPointY: Math.min(minY, maxY) - tileWidth,
            maxPointY: Math.max(minY, maxY) + tileWidth
        };
        return range;
    }


    // /**
    //  * 添加和相机碰撞的模型
    //  */
    // public addModelCollisionCamera(model: Object3D){
    //     this.modelCollisionCameraArray.push(model);
    // }

    // public getModelCollisionCamera(){
    //     return this.modelCollisionCameraArray;
    // }

    /**
     * 获取地图的俯仰角,这里返回的是角度，不是弧度
     */
    public getPitchAngle() {
        return (App.getInstance().camera.orbit.phi / Math.PI / 2) * 360;
    }

    /**
     * 获取地图的方位角,这里返回的是角度，不是弧度。方位角可以理解为相机在地图上的方向，0度是朝向正北，90度是朝向正东，-90度是朝向正西，180度或者-180是朝向正南
     * @returns 
     */
    public getHeadAngle() {
        return (App.getInstance().camera.orbit.theta / Math.PI / 2) * 360;
    }

    /**
     * 设置地图为垂直视角，就类似于二维地图
     */
    public setVerticalMap(){
        this.app.options.Map.verticalLevel= this.app.options.Map.maxZoom;
    }

    /**
     * 设置地图的倾斜角度
     */
    public setMapPitchAngle(angle){
        this.app.options.Map.verticalLevel=6;
        this.app.camera.orbit.setLookAngle(angle);
    }

    /**
     * 删除掉不在当前层级的瓦片
     */
    public removeTileNoZoom(){
        for (let i = 0; i < this.children.length; i++) {
            if (this.children[i]['removeTileNoCurZoom']) {
                this.children[i]['removeTileNoCurZoom']();
            }
        }
    }

    /**
     * 设置地图的范围, 如果传入null，则使用全球的范围
     * @param range 地图的范围，格式为{leftBottomPoint: Point, rightUpPoint: Point}
     * @param range.leftBottomPoint 地图左下角的点
     * @param range.rightUpPoint 地图右上角的点
     * @returns 
     */
    public setMapRange(range: {leftBottomPoint: Point, rightUpPoint: Point}){
        if(!range){
            Config.Map.mapRange = {minX: -Const.Number.MERCATORANGE, minY: -Const.Number.MERCATORANGE, maxX: Const.Number.MERCATORANGE, maxY: Const.Number.MERCATORANGE}
            return;
        }
        let leftBottom = range.leftBottomPoint.toEPSGWeb();
        let rightUp = range.rightUpPoint.toEPSGWeb();

        Config.Map.mapRange.minX = Math.min(leftBottom.x, rightUp.x);
        Config.Map.mapRange.maxX = Math.max(leftBottom.x, rightUp.x);
        Config.Map.mapRange.minY = Math.min(leftBottom.y, rightUp.y);
        Config.Map.mapRange.maxY = Math.max(leftBottom.y, rightUp.y);
    }

    private collisitionTime = 0;
    private executePoiCollision() {
        this.collisitionTime++;
        if (this.collisitionTime % 50 == 0) {
            PoiCollisionManager.executeCollision();
            this.collisitionTime = 0;
        }
    }

    protected register() {
        // this.app.on(Events.CameraChange, this.debounceLoadEvent, this);
        this.app.on(Events.CameraChange, this.load, this);
        this.app.on(Events.ForceRefreshMap, this.load, this);
        this.app.on(Events.ZoomChange, this.load, this);
        this.app.on(Events.Render, this.executePoiCollision, this);
        this.app.on(Events.Resize, this.load, this);
        this.app.on(Events.Dispose, this.dispose, this);
    }

    public dispose() {
       this.app.off(Events.CameraChange, this.load, this);
       this.app.off(Events.ForceRefreshMap, this.load, this);
       this.app.off(Events.ZoomChange, this.load, this);
       this.app.off(Events.Render, this.executePoiCollision, this);
       this.app.off(Events.Resize, this.load, this);
       this.app.off(Events.Dispose, this.dispose, this);

        MeshUtil.dispose(this);
    }
}

export { Map }